<?php


namespace app\admin\command;

use Doctrine\Common\Annotations\FileCacheReader;
use Illuminate\Console\Command;
use support\annotation\api\ApiDesc;
use support\annotation\api\ApiGroup;
use support\annotation\api\ApiHeader;
use support\annotation\api\ApiParam;
use support\annotation\api\ApiReturn;
use support\annotation\api\ApiReturnHeader;
use support\annotation\api\ApiReturnParam;
use support\bootstrap\Annotation;

/**
 *
 */
class ApiDoc extends Command
{
    protected $signature = 'api:doc';

    protected $description = '创建api文档';

    /**
     * @var FileCacheReader
     */
    protected $reader = null;

    public function __construct()
    {
        parent::__construct();
        $this->reader = Annotation::instance();
    }

    public function handle()
    {
        $apiDir = app_path() . DS . 'api' . DS . 'controller';
        $files = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($apiDir),
            \RecursiveIteratorIterator::LEAVES_ONLY
        );
        foreach ($files as $name => $file) {
            if (!$file->isDir() && $file->getExtension() == 'php') {
                $filePath = $file->getRealPath();
                $classes[] = $this->get_class_from_file($filePath);
            }
        }
        $classes = array_unique(array_filter($classes));
        $apiGroups = [];
        foreach ($classes as $class) {
            $reflectionClass = new \ReflectionClass($class);
            $groupKeyName = str_replace("app\\api\\controller\\", "", $class);
            /** @var $apiGroupObj ApiGroup */
            $apiGroupObj = $this->reader->getClassAnnotation($reflectionClass, ApiGroup::class);
            if (!$apiGroupObj) {
                continue;
            }
            if (!isset($apiGroups[$groupKeyName])) {
                $apiGroups[$groupKeyName] = [
                    'key' => $groupKeyName,
                    'name' => $apiGroupObj ? $apiGroupObj->name : $groupKeyName,
                    'weigh' => $apiGroupObj ? $apiGroupObj->weigh : 1000,
                    'apis' => []
                ];
            }
            $methods = $reflectionClass->getMethods();
            foreach ($methods as $method) {
                /** @var \ReflectionMethod $method */
                $annotations = $this->reader->getMethodAnnotations($method);
                $apiDesc = []; // 接口表述
                foreach ($annotations as $annotation) {
                    if ($annotation instanceof ApiDesc) {
                        $apiDesc = $this->getApiDesc($annotation, $groupKeyName, $method->name);
                    } elseif ($annotation instanceof ApiParam) {
                        $apiDesc['params'][] = $this->getApiParam($annotation);
                    } elseif ($annotation instanceof ApiReturn) {
                        $apiDesc['returns'][] = $this->getApiReturn($annotation);
                    } elseif ($annotation instanceof ApiReturnParam) {
                        $apiDesc['return_params'][] = $this->getApiReturnParam($annotation);
                    } elseif ($annotation instanceof ApiHeader) {
                        $apiDesc['headers'][] = $this->getApiHeader($annotation);
                    } elseif ($annotation instanceof ApiReturnHeader) {
                        $apiDesc['return_headers'][] = $this->getApiReturnHeader($annotation);
                    }
                }
                if (empty($apiDesc)) {
                    continue;
                }
                $apiGroups[$groupKeyName]['apis'][] = $apiDesc;
            }
        }
//        $this->output->info(print_r($apiGroups, true));
        $this->saveData($apiGroups);
        $this->output->info('success');
    }

    protected function saveData($data) {
        foreach ($data as $apiGroup) {
            $tmpSaveData = [
                'key' => $apiGroup['key'],
                'name' => $apiGroup['name'],
                'weigh' => $apiGroup['weigh']
            ];
            $apiGroupRow = $this->saveApiGroup($tmpSaveData);
            $this->saveApiDescs($apiGroup['apis'], $apiGroupRow);
        }
    }

    protected function saveApiDescs($data, $apiGroupRow) {
        foreach ($data as $apiDesc) {
            $apiDesc['group_id'] = $apiGroupRow->id;
            $this->saveApidesc($apiDesc);
        }
    }

    protected function saveApidesc($data) {
        $apiDesc = \app\admin\model\ApiDesc::query()->firstOrNew(['key' => $data['key']]);
        $apiDesc->fill($data);
        $apiDesc->save();
    }

    protected function saveApiGroup($data) {
        $apiGroup = \app\admin\model\ApiGroup::query()->firstOrNew(['key' => $data['key']]);
        $apiGroup->fill($data);
        $apiGroup->save();
        return $apiGroup;
    }

    protected function getApiDesc($annotation, $groupKeyName, $method) {
        /** @var $annotation ApiDesc */
        $apiDesc = [
            'title' => $annotation->title,
            'weigh' => $annotation->weigh,
            'summary' => $annotation->summary,
            'route' => $annotation->route,
            'method' => $annotation->method,
            'key' => $groupKeyName . ucfirst($method)
        ];
        return $apiDesc;
    }

    protected function getApiParam($annotation) {
        /** @var $annotation ApiParam */
        return [
            'field' => $annotation->field,
            'type' => $annotation->type,
            'required' => $annotation->required,
            'des'   => $annotation->des
        ];
    }

    protected function getApiReturn($annotation) {
        /** @var $annotation ApiReturn */
        return [
            'title' => $annotation->title,
            'data' => $annotation->data
        ];
    }

    protected function getApiReturnParam($annotation) {
        /** @var $annotation ApiReturnParam */
        return [
            'field' => $annotation->field,
            'type' => $annotation->type,
            'des'   => $annotation->des,
            'sample' => $annotation->sample
        ];
    }

    protected function getApiHeader($annotation) {
        /** @var $annotation ApiHeader */
        return [
            'field' => $annotation->field,
            'type' => $annotation->type,
            'required' => $annotation->required,
            'des'   => $annotation->des
        ];
    }

    protected function getApiReturnHeader($annotation) {
        /** @var $annotation ApiReturnHeader */
        return [
            'field' => $annotation->field,
            'type' => $annotation->type,
            'des'   => $annotation->des,
            'sample' => $annotation->sample
        ];
    }

    /**
     * get full qualified class name
     *
     * @param string $path_to_file
     * @return string
     * @author JBYRNE http://jarretbyrne.com/2015/06/197/
     */
    protected function get_class_from_file($path_to_file)
    {
        //Grab the contents of the file
        $contents = file_get_contents($path_to_file);

        //Start with a blank namespace and class
        $namespace = $class = "";

        //Set helper values to know that we have found the namespace/class token and need to collect the string values after them
        $getting_namespace = $getting_class = false;

        //Go through each token and evaluate it as necessary
        foreach (token_get_all($contents) as $token) {

            //If this token is the namespace declaring, then flag that the next tokens will be the namespace name
            if (is_array($token) && $token[0] == T_NAMESPACE) {
                $getting_namespace = true;
            }

            //If this token is the class declaring, then flag that the next tokens will be the class name
            if (is_array($token) && $token[0] == T_CLASS) {
                $getting_class = true;
            }

            //While we're grabbing the namespace name...
            if ($getting_namespace === true) {

                //If the token is a string or the namespace separator...
                if (is_array($token) && in_array($token[0], [T_STRING, T_NS_SEPARATOR])) {

                    //Append the token's value to the name of the namespace
                    $namespace .= $token[1];
                } elseif ($token === ';') {

                    //If the token is the semicolon, then we're done with the namespace declaration
                    $getting_namespace = false;
                }
            }

            //While we're grabbing the class name...
            if ($getting_class === true) {

                //If the token is a string, it's the name of the class
                if (is_array($token) && $token[0] == T_STRING) {

                    //Store the token's value as the class name
                    $class = $token[1];

                    //Got what we need, stope here
                    break;
                }
            }
        }

        //Build the fully-qualified class name and return it
        return $namespace ? $namespace . '\\' . $class : $class;
    }
}
